//
// Created by Never on 2019/8/6.
//

#include "WriteResults.h"

string WriteResults::doubleWithGivenPrecision(double number, int precision) {
    string res = to_string(number);
    return res.substr(0, res.find('.') + precision + 1);
}

void WriteResults::writeResults(vector<vector<vector<int>>> depotsToCustomers, vector<int> depotsToVehicleNumber,
                                vector<int> depotsToDemands, string filename) {
    Json::Value root;
    for(int i = 0; i < depotsToCustomers.size(); i++)
    {
        if(depotsToCustomers.empty())
            continue;
        string rootString = "depot "+to_string(i);
        Json::Value routes;
        for(int route = 0; route < depotsToCustomers[i].size(); route++)
        {
            string routeString = "route "+to_string(route);
            for(auto customer: depotsToCustomers[i][route])
            {
                routes[routeString].append(customer);
            }
        }
        routes["vehicle number"] = to_string(depotsToVehicleNumber[i]);
        routes["total demands"] = to_string(depotsToDemands[i]);
        root[rootString] = routes;
    }

    writeRootToFile(root, filename);
}

void WriteResults::recordFinalResult(string &resultFileName, bool isSolved, int modelTime, int exploitTrials,
                                     int exploreTrials,
                                     int totalCost, string &subTourPolicy, int lastImprovedTrail, int mode,
                                     string &modelType,
                                     string &initialAlgForLKH3, int subTourNumber, int vehiclesCost, int depotsCost,
                                     int routesCost,
                                     double capThres, double vehicleFactor, string improvementStrategy,
                                     int improvementTimeInLS,
                                     int escapeLocalOptTime, int exploreNeighborCount, double totalTime,
                                     vector<bool> solution) {
    Json::Value root;
    root["resultFileName"] = resultFileName;
    root["isSolved"] = isSolved;
    root["mode"] = to_string(mode);
    root["modelTime"] = to_string(modelTime);
    root["exploitTrials"] = to_string(exploitTrials);
    root["exploreTrials"] = exploreTrials;
    root["totalTime"] = to_string(totalTime);
    root["totalCost"] = totalCost;
    root["subTourPolicy"] = subTourPolicy;
    root["lastImprovedTrail"] = lastImprovedTrail;
    root["modelType"] = modelType;
    root["initialAlgForLKH3"] = initialAlgForLKH3;
    root["subTourNumber"] = subTourNumber;

    root["vehiclesCost"] = vehiclesCost;
    root["depotsCost"] = depotsCost;
    root["routesCost"] = routesCost;
    root["capThres"] = capThres;
    root["vehicleFactor"] = vehicleFactor;
    root["improvementStrategy"] = improvementStrategy;

    root["improvementTimeInLS"] = improvementTimeInLS;
    root["escapeLocalOptTime"] = escapeLocalOptTime;
    root["exploreNeighborCount"] = exploreNeighborCount;
    string sol;
    for(auto ele: solution)
    {
        if(ele)
            sol += '1';
        else
            sol += '0';
        sol += ' ';
    }
    root["solution"] = sol;

    writeRootToFile(root, resultFileName);

}

void WriteResults::writeRootToFile(Json::Value &root, const string &filename) {
    Json::StreamWriterBuilder sw;
    ofstream os;
    os.open(filename);
    std::unique_ptr<Json::StreamWriter> writer( sw.newStreamWriter() );
    writer->write(root, &os);
    os.close();
}

void WriteResults::recordResult(Json::Value &root, const string &suffix, const Result *result,
                                const vector<vector<int>> &initialRoutes) {
    root["costOfBestSol"+suffix] = result->getBestCost();
    recordRoutesAsArray(root, result->routes, "routes"+suffix);
    recordRoutesAsArray(root, initialRoutes, "initialRoutes"+suffix);
    root["runTimeOfBestSol"+suffix] = result->getTimeOfObtainBestSol();
    root["runTimeOfObtainBestSol"+suffix] = result->getRunTime();
#if defined(RunModeDebug)
    root["lastImprovementStep"+suffix] = result->lastImprovementStep;
    root["totalSteps"+suffix] = result->totalSteps;


    for(auto moveType: MoveTypeCollection::allMoveTypes)
        root[MoveTypeAuxFunc::moveTypeToMoveTypeName(moveType)+suffix] = result->invokeTimeOfMoves[moveType];
#endif
}

void WriteResults::recordResult(const RunParaAndResult &runParaAndResult)
{
    Json::Value root;
    NeighborSpaceOfGivenCus neighborSpace{runParaAndResult.instance, {runParaAndResult.instance.cusNum, runParaAndResult.instance.depotNum}};

    root["instance name"] = runParaAndResult.instance.instanceName;

    const auto bestResult = runParaAndResult.bestRes;
    const auto worstResult = runParaAndResult.worstRes;
    if(bestResult->isSolved()){
        recordResult(root, "", bestResult, runParaAndResult.initialRoutes);
#if defined(RunModeDebug)
        recordIntermediateInfo(root, bestResult);
        recordUnImprovementSteps(root, bestResult->unImproveSteps);
#endif
    }
    if(worstResult->isSolved())
        recordResult(root, "Worst", worstResult, runParaAndResult.initialRoutesOfWorstSol);

    root["averageCost"] = runParaAndResult.averageCost;
    root["averageTime"] =  runParaAndResult.averageTime;
    root["averageTimeOfObtainBestSol"] =  runParaAndResult.averageTimeForBestSol;
    root["countOfBestSol"] = runParaAndResult.countOfBestSol;
    for(auto seed: runParaAndResult.randomSeeds)
        root["randomSeeds"].append(seed);
    const auto &parameter = runParaAndResult.parameterForRoutesRelatedProblem;
    const auto &parForGTS = parameter.parameterForGranularTabuSearch;
    switch(runParaAndResult.parameterForRoutesRelatedProblem.parameterForSolveStrategy.solveStrategy)
    {
        case LocalSearch:
#if defined(RunModeDebug)
            for(const auto &weight: bestResult->finalWeights)
                root["finalWeights"].append(weight);
#endif
            break;
        case ModelWithLocalSearch:
            root["bestSolInRoutesPool"] = bestResult->bestSolInRoutesPool;
            root["feasibleSolCount"] = bestResult->feasibleSolNum;
            root["invokeLSCount"] = bestResult->invokeLSNum;
            root["routesPoolSize"] = bestResult->routesPoolSize;
            root["isOptimumFound"] = bestResult->isOptimumFound;
            break;
        case HybridEvoAlgorithm:
            root["energizeNum"] = bestResult->energizeNum;
            root["crossoverNum"] = bestResult->crossoverNum;
            root["lastImprovedCrossover"] = bestResult->lastImprovedCrossover;
    }
    const auto processOfObj = bestResult->obtainObjOfEvolution();
    const auto processOfTime = bestResult->obtainTimeOfEvolution();
    for(int i = 1; i < processOfObj.size(); i++){
        root["completeRes"].append(processOfObj[i]);
        root["completeTime"].append(difftime(processOfTime[i], bestResult->startTime));
    }
    int lastIndex = 0;
    for(int i = 1; i < processOfObj.size(); i++){
        if(lastIndex == 0 or
           (difftime(processOfTime[i], processOfTime[lastIndex]) >= 1)){
            root["recordRes"].append(processOfObj[i]);
            root["recordTime"].append(difftime(processOfTime[i], bestResult->startTime));
            lastIndex = i;
        }
    }
    if(lastIndex != processOfObj.size()-1){
        root["recordRes"].append(bestResult->getBestCost());
        root["recordTime"].append(difftime(bestResult->getTimeOfSolImproved(), bestResult->startTime));
    }

    writeRootToFile(root, runParaAndResult.resultFileName);
    root.clear();
    analyseRoutes(root, "bestInitial", runParaAndResult.initialRoutes, neighborSpace, runParaAndResult.instance);
    analyseRoutes(root, "bestResult", bestResult->routes, neighborSpace, runParaAndResult.instance);
    analyseRoutes(root, "worstInitial", runParaAndResult.initialRoutesOfWorstSol, neighborSpace, runParaAndResult.instance);
    analyseRoutes(root, "worstResult", worstResult->routes, neighborSpace, runParaAndResult.instance);
    distanceBetCusToAllDepots(root, runParaAndResult.instance.distances, runParaAndResult.instance.depotNum,
                              runParaAndResult.instance.cusNum);
    writeRootToFile(root, runParaAndResult.resultFileName+"_analyse.json");
}

void WriteResults::analyseRoutes(Json::Value &root, const string &key, const vector<vector<int>> &routes,
                                 const NeighborSpaceOfGivenCus &neighborSpace, const Instance &ins) {
    vector<vector<int>> cusToNeighRank(ins.cusNum, vector<int>(2));
    vector<int> cusToDepotRank(ins.cusNum);

    for(const auto& route: routes){
        for(int i = 1; i < route.size()-1; i++){
            cusToNeighRank[route[i]-ins.depotNum][0] = neighborSpace.rankOfNodeWToCusV(route[i], route[i-1]);
            cusToNeighRank[route[i]-ins.depotNum][1] = neighborSpace.rankOfNodeWToCusV(route[i], route[i+1]);
            cusToDepotRank[route[i]-ins.depotNum] = neighborSpace.rankOfNodeWToCusV(route[i], route.front());
        }
        cusToNeighRank[route.back()-ins.depotNum][0] = neighborSpace.rankOfNodeWToCusV(route.back(), route[route.size()-2]);
        cusToNeighRank[route.back()-ins.depotNum][1] = neighborSpace.rankOfNodeWToCusV(route.back(), route.front());
        cusToDepotRank[route.back()-ins.depotNum] = neighborSpace.rankOfNodeWToCusV(route.back(), route.front());
    }
    root[key]["cusToNeighAndDepotRank"].append("cusId   rank1    rank2    depotRank");
    for(int i = 0; i < ins.cusNum; i++)
        root[key]["cusToNeighAndDepotRank"].append(to_string(i + ins.depotNum) + "  " +
                                           to_string(cusToNeighRank[i][0]) + "  " +
                                           to_string(cusToNeighRank[i][1]) + "  " +
                                           to_string(cusToDepotRank[i]));
}

void WriteResults::distanceBetCusToAllDepots(Json::Value &root, const vector<vector<DisType>> &dis, int depotCount,
                                             int cusCount) {
    for(int i = depotCount; i < depotCount+cusCount; i++){
        string tmp = to_string(i-depotCount)+"   ";
        for(int j = 0; j < depotCount; j++)
            tmp += to_string(dis[i][j])+"   ";
        root["cusToDepotDis"].append(tmp);
    }
}

void WriteResults::recordRoutesAsArray(Json::Value &root, const vector<vector<int>> &routes, const string &key)
{
    for(const auto& route: routes)
    {
        string sRoute;
        for(auto node: route)
            sRoute += to_string(node) + " ";
        root[key].append(sRoute);
    }
}

#if defined(RunModeDebug)

void WriteResults::recordIntermediateInfo(Json::Value &root, const Result* result){
    for(int i = 0; i < result->allGenObjFeaCostAndPenaltyPenaltyFactorAndMoveType.size(); i++){
        string maxGenPerStr;
        string maxGenDeltaStr;
        string minGenPerStr;
        string minGenDeltaStr;
        double maxGenPer = INT_MIN;
        double maxGenDelta = INT_MIN;
        double minGenPer = INT_MAX;
        double minGenDelta = INT_MAX;

        string maxObjPerStr;
        string maxObjDeltaStr;
        string minObjPerStr;
        string minObjDeltaStr;
        double maxObjPer = INT_MIN;
        double maxObjDelta = INT_MIN;
        double minObjPer = INT_MAX;
        double minObjDelta = INT_MAX;

        root["statistic info"].append(string("best     curGen   curObj   pen         penFac      fea per delta moveType"));
        for(int j = 0; j < result->allGenObjFeaCostAndPenaltyPenaltyFactorAndMoveType[i].size(); j++) {
            string penalty;
            for(const auto& p: get<2>(result->allGenObjFeaCostAndPenaltyPenaltyFactorAndMoveType[i][j]).penalties)
                penalty += doubleWithGivenPrecision(p)+" ";
            string penaltyFac;
            for(const auto cons: constraints)
                penaltyFac += doubleWithGivenPrecision(get<3>(result->allGenObjFeaCostAndPenaltyPenaltyFactorAndMoveType[i][j]).penaltyFac(cons)) + " ";
            string res = doubleWithGivenPrecision(get<0>(result->bestSolAndDeltaAndPercentageComparedWithBestSol[i][j])) + " " +
                    doubleWithGivenPrecision(get<0>(result->allGenObjFeaCostAndPenaltyPenaltyFactorAndMoveType[i][j])) + " " +
                    doubleWithGivenPrecision(get<2>(result->allGenObjFeaCostAndPenaltyPenaltyFactorAndMoveType[i][j]).totalCost) + " ";
            res += penalty + penaltyFac+
                    (get<1>(result->allGenObjFeaCostAndPenaltyPenaltyFactorAndMoveType[i][j]) ? "1" : "0") + " " +
                    doubleWithGivenPrecision(get<2>(result->bestSolAndDeltaAndPercentageComparedWithBestSol[i][j])) + " " +
                    doubleWithGivenPrecision(get<1>(result->bestSolAndDeltaAndPercentageComparedWithBestSol[i][j])) + " " +
                    MoveTypeAuxFunc::moveTypeToMoveTypeName(get<4>(result->allGenObjFeaCostAndPenaltyPenaltyFactorAndMoveType[i][j]));
            root["statistic info"].append(res);
            if(isEqual(get<0>(result->bestSolAndDeltaAndPercentageComparedWithBestSol[i][j]), get<0>(result->allGenObjFeaCostAndPenaltyPenaltyFactorAndMoveType[i][j]))){
                if(not isEqual(maxGenPer, INT_MIN)){
                    maxGenPerStr += doubleWithGivenPrecision(maxGenPer) + " ";
                    maxGenDeltaStr += doubleWithGivenPrecision(maxGenDelta) + " ";
                    minGenPerStr += doubleWithGivenPrecision(minGenPer) + " ";
                    minGenDeltaStr += doubleWithGivenPrecision(minGenDelta) + " ";

                    maxGenPer = INT_MIN;
                    maxGenDelta = INT_MIN;
                    minGenPer = INT_MAX;
                    minGenDelta = INT_MAX;

                    maxObjPerStr += doubleWithGivenPrecision(maxObjPer) + " ";
                    maxObjDeltaStr += doubleWithGivenPrecision(maxObjDelta) + " ";
                    minObjPerStr += doubleWithGivenPrecision(minObjPer) + " ";
                    minObjDeltaStr += doubleWithGivenPrecision(minObjDelta) + " ";

                    maxObjPer = INT_MIN;
                    maxObjDelta = INT_MIN;
                    minObjPer = INT_MAX;
                    minObjDelta = INT_MAX;
                }
            }
            else if(not isEqual(get<0>(result->bestSolAndDeltaAndPercentageComparedWithBestSol[i][j]), INT_MAX)){
                maxGenPer = max(maxGenPer, get<2>(result->bestSolAndDeltaAndPercentageComparedWithBestSol[i][j]));
                maxGenDelta = max(maxGenDelta, get<1>(result->bestSolAndDeltaAndPercentageComparedWithBestSol[i][j]));
                minGenPer = min(minGenPer, get<2>(result->bestSolAndDeltaAndPercentageComparedWithBestSol[i][j]));
                minGenDelta = min(minGenDelta, get<1>(result->bestSolAndDeltaAndPercentageComparedWithBestSol[i][j]));

                auto delta = get<2>(result->allGenObjFeaCostAndPenaltyPenaltyFactorAndMoveType[i][j]).totalCost -
                             get<0>(result->bestSolAndDeltaAndPercentageComparedWithBestSol[i][j]);
                auto per = delta/get<0>(result->bestSolAndDeltaAndPercentageComparedWithBestSol[i][j]);
                maxObjPer = max(maxObjPer, per);
                maxObjDelta = max(maxObjDelta, delta);
                minObjPer = min(minObjPer, per);
                minObjDelta = min(minObjDelta, delta);
            }
        }
        if(not isEqual(maxGenPer, INT_MIN)){
            maxGenPerStr += doubleWithGivenPrecision(maxGenPer) + " ";
            maxGenDeltaStr += doubleWithGivenPrecision(maxGenDelta) + " ";
            minGenPerStr += doubleWithGivenPrecision(minGenPer) + " ";
            minGenDeltaStr += doubleWithGivenPrecision(minGenDelta) + " ";

            maxObjPerStr += doubleWithGivenPrecision(maxObjPer) + " ";
            maxObjDeltaStr += doubleWithGivenPrecision(maxObjDelta) + " ";
            minObjPerStr += doubleWithGivenPrecision(minObjPer) + " ";
            minObjDeltaStr += doubleWithGivenPrecision(minObjDelta) + " ";
        }
        root["maxGenPer"].append(maxGenPerStr);
        root["maxGenDelta"].append(maxGenDeltaStr);
        root["minGenPer"].append(minGenPerStr);
        root["minGenDelta"].append(minGenDeltaStr);

        root["maxObjPer"].append(maxObjPerStr);
        root["maxObjDelta"].append(maxObjDeltaStr);
        root["minObjPer"].append(minObjPerStr);
        root["minObjDelta"].append(minObjDeltaStr);
    }
}

void WriteResults::recordUnImprovementSteps(Json::Value &root, const vector<vector<int>> &unImproveSteps) {
    for(const auto& step: unImproveSteps){
        string res;
        for(auto e: step)
            res += to_string(e) + " ";
        root["unImproSteps"].append(res);
    }
}

#endif

bool WriteResults::isFileExist(const string &filename) {
    ifstream f(filename);
    return f.good();
}

bool WriteResults::isResultImproved(const RunParaAndResult &runParaAndResult) {
    if(isFileExist(runParaAndResult.resultFileName)){
        fstream fs;
        fs.open(runParaAndResult.resultFileName, ios::in);
        Json::Value value;
        Json::Reader jsonReader;
        if (jsonReader.parse(fs,value)){
            if (isFirstLessThanSecond(DisType(value["costOfBestSol"].asDouble()), runParaAndResult.bestRes->getBestCost()) or
                (isEqual(DisType(value["costOfBestSol"].asDouble()), runParaAndResult.bestRes->getBestCost()) and
                        isFirstLessThanSecond(DisType(value["averageCost"].asDouble()), runParaAndResult.averageCost)) or
                    (isEqual(DisType(value["costOfBestSol"].asDouble()), runParaAndResult.bestRes->getBestCost()) and
                     isEqual(DisType(value["averageCost"].asDouble()), runParaAndResult.averageCost) and
                     isFirstLessThanSecond(value["averageTimeOfObtainBestSol"].asDouble(), runParaAndResult.averageTimeForBestSol))) {
                LOG(INFO) << "previous results are better";
                return false;
            }
        }
    }
    ofstream os;
    os.open(runParaAndResult.resultFileName+"_indicator", ios::out);
    os.close();
    return true;
}
